﻿#include "stdafx.h"
#include "sbcsx.h"
#include "extend/sbvalues.h"
#if !_SB_WINDOWS_
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#if 1 && _SB_DEBUG
#define SB_HFS_TRACE		0
#endif

//////////////////////////////////////////////////////////////////////////
// 목록
KIMPL_SUBCLASS_ABSTRACT(kFileList, "FileList");

kFileList::kFileList()
: _infos(new FileArray)
{
}

kFileList::~kFileList()
{
	if (_mount)
		_mount->Unload();

	delete _infos;
}

bool kFileList::First(kFileInfo* info)
{
	if (_infos->IsEmpty())
		return false;

	_index = 0;

	if (info)
		*info = (*_infos)[0];

	return true;
}

bool kFileList::Next(kFileInfo* info)
{
	_index++;

	if (_index < _infos->Count())
	{
		if (info)
			*info = (*_infos)[_index];

		return true;
	}
	else
	{
		// -1로 하는 이유는 다시 호출했을 때 반복 동작을 꾀함
		_index = -1;
		return false;
	}
}

bool kFileList::Current(kFileInfo* info)
{
	if ((kuint)_index >= (kuint)_infos->Count())
		return false;
	else
	{
		if (info)
			*info = (*_infos)[_index];

		return true;
	}
}

kint kFileList::Count()
{
	return _infos->Count();
}

kint kFileList::Index()
{
	return _index;
}


//////////////////////////////////////////////////////////////////////////
// 마운트
KIMPL_SUBCLASS_ABSTRACT(kMount, "Mount");

kMount::kMount()
: _path(K_PATH_SEP)
{
}

kMount::~kMount()
{

}

kStream* kMount::FileOpen(kMount* mnt, const kchar* filename, const kchar* mode)
{
	k_return_value_if_fail(filename, NULL);

	if (!mnt)
		return kFileStream::New(filename, mode);
	else
		return mnt->OpenFile(filename, mode);
}

kStream* kMount::FileOpenIndirect(kMount* mnt, const kchar* filename)
{
	k_return_value_if_fail(filename, NULL);

	if (!mnt)
		return kFileStream::New(filename);
	else
		return mnt->OpenFileIndirect(filename);
}

kpointer kMount::FileRead(kMount* mnt, const kchar* filename, kint* size)
{
	k_return_value_if_fail(filename, NULL);

	if (!mnt)
		return k_file_alloc(filename, size);
	else
		return mnt->ReadFile(filename, size);
}

kchar* kMount::FileTextRead(kMount* mnt, const kchar* filename, kint* size, kint* codepage)
{
	kint filesize;
	kchar* data = (kchar*)FileRead(mnt, filename, &filesize);

	if (!data)
	{
		if (size)
			*size = 0;

		if (codepage)
			*codepage = 0;

		return NULL;
	}

	kchar* ps, *pd;
	kuint hdr4 = *(kuint*)data;
	kushort hdr2 = *(kushort*)data;

	if (hdr2 == 0xFEFF)
	{
		// UTF-16 LE
		if (size)
			*size = (filesize - 2) / 2;

		if (codepage)
			*codepage = KACP_UTF16;

		kchar* conv = k_new(filesize + 10, char);

		for (ps = data + 2, pd = conv, filesize -= 2; filesize > 0; filesize--)
		{
			*pd = *ps;
			++pd;
			++ps;
		}

		*(kushort*)pd = L'\0';

		k_delete(data);
		data = conv;
	}
	else if (hdr2 == 0xFFFE)
	{
		// UTF-16 BE
		if (size)
			*size = (filesize - 2) / 2;

		if (codepage)
			*codepage = KACP_UTF16BE;

		kchar* conv = k_new(filesize + 10, char);

		for (ps = data + 2, pd = conv, filesize -= 2; filesize > 0; filesize -= 2)
		{
			*pd = *(ps + 1);
			*(pd + 1) = *ps;
			pd += 2;
			ps += 2;
		}

		*(kushort*)pd = L'\0';

		k_delete(data);
		data = conv;
	}
	else if ((hdr4 & 0x00FFFFFF) == 0x00BFBBEF)
	{
		// UTF-8
		if (size)
			*size = filesize - 3;

		if (codepage)
			*codepage = KACP_UTF8;

		kchar* conv = k_new(filesize, char);
		memcpy(conv, data + 3, filesize - 3);
		conv[filesize - 3] = '\0';

		k_delete(data);
		data = conv;
	}
	else
	{
		// 음
		if (size)
			*size = filesize;

		if (codepage)
			*codepage = KACP_OEM; // HBCP_UTF8 로 할까?

		data[filesize] = '\0';
	}

	return data;
}

kchar* kMount::BuildName(kMount* mnt, const kchar* path)
{
	k_return_value_if_fail(path, NULL);

	if (!mnt)
		return k_strdup(path);
	else
	{
		kchar* name;
		k_asprintf(&name, "%s:%s", mnt->_desc.Data(), path);

		return name;
	}
}

kchar* kMount::BuildPath(kMount* mnt, const kchar* path)
{
	return !mnt ? k_strdup(path) : mnt->ExtractPath(path);
}


//////////////////////////////////////////////////////////////////////////
// 디스크 마운트
KIMPL_SUBCLASS(kDiskFs, "DiskFs");

// fake
class kDiskFsFake : public kDiskFs
{
public:
	PathString GetRealPath(const char* path)
	{
		PathString bs(_desc);

		if (*path != '/' && *path != '\\')
			bs += _path;

		bs += path;

		return bs;
	}

	bool InitDir(const kchar* dir)
	{
#if _SB_WINDOWS_
		if (dir)
		{
			kwchar uni[MAX_PATH], abspath[MAX_PATH];
			k_conv_utf8_to_utf16(uni, MAX_PATH - 1, dir, 0);
			_wfullpath(abspath, uni, MAX_PATH);

pos_retry:
			WIN32_FIND_DATA ffd;
			HANDLE h = FindFirstFileEx(abspath, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);

			if (h == INVALID_HANDLE_VALUE)
			{
				ksize_t len = k_wcslen(abspath);

				if (len == 3 &&
					abspath[1] == L':' &&
					(abspath[2] == L'\\' || abspath[2] == L'/'))
				{
					// 이건 오케
					// 비록 드라이브 이름은 확인할 수 없지만..
				}
				else
				{
					// 변경할 수 없음
					if (abspath[len - 1] == L'/' || abspath[len - 1] == L'\\')
					{
						// 혹시 끝에 슬래시면 파일 찾기가 안되므로 제거하고 다시
						abspath[len - 1] = L'\0';
						goto pos_retry;
					}

					return false;
				}
			}
			else
			{
				FindClose(h);

				if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
					return false;
			}

			_desc = abspath;
		}
		else
		{
			kwchar uni[MAX_PATH];
			GetCurrentDirectory(MAX_PATH, uni);
			_desc = uni;
		}

		int ch = *_desc.Last();

		if (ch == '/' || ch == '\\')
		{
			// 이건 표준 함수로 안되네
			//_desc._data[--_desc._len] = '\0';
			_desc.Truncate(_desc.Length() - 1);
		}
#else
		if (dir)
		{
			struct stat s;
			char tdir[260];

			k_strncpy(tdir, dir, 259);

pos_retry:
			kint n = stat(tdir, &s);

			if (n < 0)
			{
				ksize_t len = k_strlen(tdir);

				if (len == 1 && (tdir[0] == '/' || tdir[0] == '\\'))
				{
					// 이건 오케
				}
				else
				{
					if (tdir[len - 1] == '/' || tdir[len - 1] == '\\')
					{
						// 혹시 끝에 슬래시면 파일 찾기가 안되므로 제거하고 다시
						tdir[len - 1] = '\0';
						goto pos_retry;
					}

					return false;
				}
			}
			else
			{
				if ((s.st_mode & 0x4000) == 0)
					return false;
			}

			_desc = tdir;
		}
		else
		{
			if (!getcwd(_desc._data, 259 - 1))
			{
				// gcc 4.5 부터 리턴값 검사해야 함.
				return false;
			}

			_desc.Intern();
		}

		int ch = *_desc.Last();

		if (ch == '/' || ch == '\\')
		{
			// 이건 표준 함수로 안되네
			_desc._data[--_desc._len] = '\0';
		}
#endif

		return true;
	}
};

//
kDiskFs::kDiskFs()
{

}

//
kDiskFs::~kDiskFs()
{

}

//
kint kDiskFs::IsExist(const kchar* path)
{
	k_return_value_if_fail(path, KFAS_NONE);

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(path);

#if _SB_WINDOWS_
	kwchar uni[MAX_PATH];
	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, bs.Data(), 0);

	WIN32_FIND_DATA ffd;
	HANDLE h = FindFirstFileEx(uni, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);

	if (h == INVALID_HANDLE_VALUE)
		return KFAS_NONE;
	else
	{
		FindClose(h);

		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			return KFAS_DIR;
		else
			return KFAS_FILE;
	}
#else
	struct stat s;
	kint n = stat(bs.Data(), &s);

	if (n < 0)
		return KFAS_NONE;
	else
	{
		if ((s.st_mode & 0x4000) != 0)
			return KFAS_DIR;
		else
			return KFAS_FILE;
	}
#endif
}

kbool kDiskFs::Remove(const kchar* path)
{
	k_return_value_if_fail(path, false);

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(path);

#if _SB_WINDOWS_
	kwchar uni[MAX_PATH];
	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, bs.Data(), 0);

	WIN32_FIND_DATA ffd;
	HANDLE h = FindFirstFileEx(uni, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);

	if (h == INVALID_HANDLE_VALUE)
		return false;
	else
	{
		FindClose(h);

		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			return RemoveDirectory(uni) != 0;
		else
			return DeleteFile(uni) != 0;
	}
#else
	struct stat s;
	kint n = stat(bs.Data(), &s);

	if (n < 0)
		return false;
	else
	{
		if ((s.st_mode & 0x4000) != 0)
			return rmdir(bs.Data()) == 0;
		else
			return remove(bs.Data()) == 0;
	}
#endif
}

kbool kDiskFs::SetDir(const kchar* dir)
{
	k_return_value_if_fail(dir, false);

	if ((*dir == '/' || *dir == '\\') && dir[1] == '\0')
	{
		_path = K_PATH_SEP;
		return true;
	}

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(dir);

#if _SB_WINDOWS_
	kwchar uni[MAX_PATH], abspath[MAX_PATH];
	WIN32_FIND_DATA ffd;
	HANDLE h;

	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, bs.Data(), 0);
	_wfullpath(abspath, uni, MAX_PATH);

	h = FindFirstFileEx(abspath, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);

	if (h == INVALID_HANDLE_VALUE)
		return false;
	else
	{
		FindClose(h);

		if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
			return false;
	}

	bs = abspath;

	if (bs.CompareLengthTo(_desc, _desc.Length()) != 0)
	{
		_path = K_PATH_SEP;
		return false;
	}

	if (bs[_desc.Length()] == '\0')
		_path = K_PATH_SEP;
	else
	{
		_path = bs.Data() + _desc.Length();
		kint ch = *_path.Last();

		if (ch != '/' && ch != '\\')
			_path += K_PATH_SEP;
	}
#else
	struct stat s;

	kint n = stat(bs.Data(), &s);

	if (n < 0)
		return false;
	else
	{
		if ((s.st_mode & 0x4000) == 0)
			return false;
	}

	char save[260];

	if (!getcwd(save, 259))
		return false;

	if (chdir(bs.Data()) != 0)
		return false;

	if (!getcwd(bs._data, 259) || chdir(save) != 0)
		return false;

	bs.Intern();

	if (bs.CompareLengthTo(_desc, _desc.Length())!=0)
	{
		_path = K_PATH_SEP;
		return false;
	}

	if (bs[_desc.Length()] == '\0')
		_path = K_PATH_SEP;
	else
	{
		_path = bs.Data() + _desc.Length();
		kint ch = *_path.Last();

		if (ch != '/' && ch != '\\')
			_path += K_PATH_SEP;
	}
#endif

	return true;
}

kbool kDiskFs::MakeDir(const kchar* dir)
{
	k_return_value_if_fail(dir, false);

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(dir);

#if _SB_WINDOWS_
	kwchar uni[MAX_PATH], abspath[MAX_PATH];
	WIN32_FIND_DATA ffd;
	HANDLE h;

	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, bs.Data(), 0);
	_wfullpath(abspath, uni, MAX_PATH);

	h = FindFirstFileEx(abspath, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);

	if (h != INVALID_HANDLE_VALUE)
	{
		FindClose(h);

		return false;
	}

	return CreateDirectory(abspath, NULL) != 0;
#else
	struct stat s;

	kint n = stat(bs.Data(), &s);

	if (n == 0)
		return false;

	return mkdir(bs.Data(), 0777) == 0;
#endif
}

kStream* kDiskFs::OpenFile(const kchar* path, const kchar* mode)
{
	return kFileStream::NewMount(path, mode, this);
}

kStream* kDiskFs::OpenFileIndirect(const char* path)
{
	return kDiskFs::OpenFile(path, NULL);
}

kpointer kDiskFs::ReadFile(const kchar* path, kint* size)
{
	k_return_value_if_fail(path, NULL);

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(path);

	return k_file_alloc(bs.Data(), size);
}

//
class kFileListDiskFs : public kFileList
{
	KCH_AUTOCLASS(kFileListDiskFs, kFileList, "FileListDiskFs");

public:
	kFileListDiskFs()
	{
	}

	void Init(kDiskFs* mnt, const kchar* mask)
	{
		kDiskFsFake::PathString path(mnt->GetDesc());
		path += mnt->GetDir();

#if _SB_WINDOWS_
		kint ch = *path.last();

		if (ch != '/' && ch != '\\')
			path += K_PATH_SEP;

		path += mask;

		kwchar uni[MAX_PATH];
		k_conv_utf8_to_utf16(uni, MAX_PATH - 1, path.Data(), 0);

		WIN32_FIND_DATA ffd;
		HANDLE h = FindFirstFileEx(uni, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);
		bool b = h != INVALID_HANDLE_VALUE;

		while (b)
		{
			kFileInfo info;

			info.type = (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? KFAS_DIR : KFAS_FILE;
			info.len = (kushort)k_conv_utf16_to_utf8(info.name, 256 - 1, ffd.cFileName, 0);

			((kvlong&)info.size).dw.l = ffd.nFileSizeLow;
			((kvlong&)info.size).dw.h = ffd.nFileSizeHigh;
			info.cmpr = 0;

			info.stc = FileTimeToStamp(ffd.ftCreationTime);
			info.stw = FileTimeToStamp(ffd.ftLastWriteTime);

			_infos->Add(info);
			b = FindNextFile(h, &ffd) != 0;
		}

		if (h != INVALID_HANDLE_VALUE)
			FindClose(h);

		_mount = mnt;
		mnt->Load();
#else
		kint ch = *path.Last();

		if (ch == '/' || ch == '\\')
		{
			*path.Last() = '\0';
			path._len--;
		}

		DIR* pd = opendir(path.Data());

		if (pd)
		{
			for (;;)
			{
				struct dirent* ent = readdir(pd);

				if (!ent)
					break;

				if (!k_strwcmatch(mask, ent->d_name))
					continue;

				kFileInfo info;

				k_strncpy(info.name, ent->d_name, 256 - 1);
				info.len = (kushort)k_strlen(info.name);

				struct stat s;
				kint n = stat(info.name, &s);

				if (n == 0)
				{
					info.type = ((s.st_mode & 0x4000) != 0) ? KFAS_DIR : KFAS_FILE;

					info.size = s.st_size;
					info.cmpr = 0;

					info.stc = TimeTToStamp(s.st_mtime);
					info.stw = TimeTToStamp(s.st_mtime);
				}
				else
				{
					info.type = 0;
					info.size = 0;
					info.cmpr = 0;
					info.stc = 0;
					info.stw = 0;
				}

				_infos->Add(info);
			}

			closedir(pd);
		}

		_mount = mnt;
		mnt->Load();
#endif
	}

#if _SB_WINDOWS_
	static kulong FileTimeToStamp(FILETIME& ft)
	{
		SYSTEMTIME st;
		FileTimeToSystemTime(&ft, &st);

		kDateTime dt;
		dt.year = st.wYear;
		dt.month = st.wMonth;
		dt.day = st.wDay;
		dt.dow = st.wDayOfWeek;
		dt.hour = st.wHour;
		dt.minute = st.wMinute;
		dt.second = st.wSecond;
		dt.millisecond = st.wMilliseconds;

		return dt.stamp;
	}
#else
	static kulong TimeTToStamp(time_t tt)
	{
		struct tm tm;
		k_localtime(&tm, &tt);

		kDateTime dt;
		dt.year = tm.tm_year + 1900;
		dt.month = tm.tm_mon + 1;
		dt.day = tm.tm_mday;
		dt.dow = tm.tm_wday;
		dt.hour = tm.tm_hour;
		dt.minute = tm.tm_min;
		dt.second = tm.tm_sec;
		dt.millisecond = 0;

		return dt.stamp;
	}
#endif
};

//
kFileList* kDiskFs::Files(const kchar* mask)
{
	if (!mask)
		mask = "*";

	kFileListDiskFs* ffs = kobj_create(kFileListDiskFs);
	ffs->Init(this, mask);

	return ffs;
}

//
kchar* kDiskFs::ExtractPath(const kchar* path)
{
	k_return_value_if_fail(path, NULL);

	PathString bs = static_cast<kDiskFsFake*>(this)->GetRealPath(path);

	char* psz = k_new(bs.Length(), char);
	k_strcpy(psz, bs.Data());

	return psz;
}

//
kDiskFs* kDiskFs::New(const kchar* dir)
{
	kDiskFs* self = kobj_create(kDiskFs);

	if (!static_cast<kDiskFsFake*>(self)->InitDir(dir))
	{
		self->Unload();
		self = NULL;
	}

	return self;
}


//////////////////////////////////////////////////////////////////////////
// HFS
KIMPL_SUBCLASS(kHfs, "Hfs");

// 헤더
#define K_HFS_HEADER			K_MAKEFOURCC('H','F','S',0)
#define K_HFS_VERSION			K_MAKEWORD(0x23, 0x01)
#define K_HFS_REVISION			K_MAKEWORD(0x00, 0x01)

// 위치
#define K_HFS_AT_SPEC			K_OFFSETOF(kFileSource, spec)
#define K_HFS_AT_TYPE			K_OFFSETOF(kFileSource, type)
#define K_HFS_AT_SIZE			K_OFFSETOF(kFileSource, size)
#define K_HFS_AT_CMPR			K_OFFSETOF(kFileSource, cmpr)
#define K_HFS_AT_SRT			K_OFFSETOF(kFileSource, srt)
#define K_HFS_AT_EXT			K_OFFSETOF(kHfsFile, ext)
#define K_HFS_AT_LEN			K_OFFSETOF(kHfsFile, len)
#define K_HFS_AT_HASH           K_OFFSETOF(kHfsFile, hash)
#define K_HFS_AT_META			K_OFFSETOF(kHfsFile, meta)
#define K_HFS_AT_NEXT			K_OFFSETOF(kHfsFile, next)
#define K_HFS_AT_STZ			K_OFFSETOF(kHfsFile, stz)
#define K_HFS_AT_ROOT			sizeof(tcm::Header)

// 도구
namespace tcm
{
	// 헤더
	struct Header
	{
		kuint			hdr;
		kushort			version;
		kushort			revision;

		kushort			signiture;
		kushort			method;
		kushort			encode;
		kushort			country;

		kulong			mask;
	};

	// 최적화용
	struct OptParam
	{
		kHfs*			src;
		kHfs*			dst;
		kuint			stack;

		tpl::Function<void(int, const kchar*)>fn;
	};

	// 옵트
	static void ProcessOptimize(OptParam prm)
	{
		const kHfs::FileArray* srcs = prm.src->GetHandles();
		kHfs::FileArray fls;

		for (auto it = srcs->cbegin(); it != srcs->cend(); ++it)
			fls.Add(*it);

		for (auto it = fls.begin(); it != fls.end(); ++it)
		{
			if (it->name[0] == '.')
				continue;

			if (it->src.type & KFAS_DIR)
			{
				prm.fn(prm.stack, it->name);

				prm.dst->MakeDir(it->name);
				prm.dst->SetDir(it->name);
				prm.src->SetDir(it->name);
				prm.stack++;

				ProcessOptimize(prm);

				prm.stack--;
				prm.dst->SetDir("..");
				prm.src->SetDir("..");
			}
			else if (it->src.type & KFAS_FILE)
			{
				prm.fn(-1, it->name);

				kint size;
				kpointer data = prm.src->ReadFile(it->name, &size);
				prm.dst->StoreBuffer(it->name, data, size, it->src.type & KFAS_CMPR ? KSTORE_CMPR : KSTORE_NORM);
				k_delete(data);
			}
		}
	}

	// 파일 이름 마스크
	static void MaskName(const kMount::PathString& name, kchar* dest)
	{
		for (int n = 0; n < 256; n++)
			dest[n] = (char)k_rand_int(0, 255);

		k_copymask(dest, name.Data(), name.Length(), 0);
		dest[name.Length()] = '\0';
	}

	// 언마스크
	static void UnmaskName(const kchar* name, kint len, kchar* dest)
	{
		k_copymask(dest, name, len, 1);
		dest[len] = '\0';
	}

	// 스플릿
	static void SplitPath(const kchar* path, tpl::BlueStr<260>& dir, tpl::BlueStr<260>& file)
	{
		k_divpath(path, dir.Data(), 260 - 1, file.Data(), 260 - 1);
		dir.Intern();
		file.Intern();
	}

	// 파일 헤더
	static kushort WriteFileHeader(kStream* st, kHfsFile& info, const kMount::PathString& name)
	{
		info.len = (kushort)name.Length();
		info.hash = (kuint)name.Hash();

		MaskName(name, info.name);
		st->Write(&info, 0, sizeof(kHfsFile));

		return info.len;
	}

	// 디렉터리
	static kushort WriteDirHeader(kStream* st, const kMount::PathString& name, kuint next, kuint meta, kulong stamp)
	{
		kHfsFile info;

		if (stamp == 0)
			k_now((kDateTime*)&stamp);

		// 개변조
		kbyte* ptr = (kbyte*)&info;
		for (ksize_t n = 0; n < sizeof(kHfsFile); n++)
			ptr[n] = (char)k_rand_int(0, 255);

		//
		info.src.spec = KFSTYLE_SYSTEM;
		info.src.type = KFAS_DIR;

		info.ext = 0;
		info.meta = meta;
		info.next = next;
		info.stz = stamp;

		return WriteFileHeader(st, info, name);
	}

	// 만들기
	static bool CreateHfs(const kchar* filename)
	{
		kStream* st = kFileStream::New(filename, "w@+");
		k_return_value_if_fail(st, false);

		kCpInfo cp;
		k_getcp(&cp);

		// 헤더
		Header hdr;
		hdr.hdr = K_HFS_HEADER;
		hdr.version = K_HFS_VERSION;
		hdr.revision = K_HFS_REVISION;
		hdr.signiture = K_MAKEWORD('S', 'B');
		hdr.method = 0;
		hdr.encode = KACP_UTF8;
		hdr.country = (kushort)cp.chcp;
		hdr.mask = 0;
		st->Write(&hdr, 0, sizeof(Header));

		// 디렉터리
		WriteDirHeader(st, ".", 0, K_HFS_AT_ROOT, 0);

		st->Unload();

		return true;
	}

	// 열기
	static kStream* OpenHfs(const kchar* filename, bool canwrite, bool usemem, bool isext)
	{
		kStream* st;
		Header hdr;

		if (usemem)
		{
			if (isext)
			{
				// 외부 HFS를 메모리로
				kStream* ex = kFileStream::New(filename, "r");
				k_return_value_if_fail(ex, NULL);

				kbyte buf[64 * 1024];
				kint size;

				if (ex->Read(&hdr, 0, sizeof(Header)) != sizeof(Header) ||
					hdr.hdr != K_HFS_HEADER || hdr.version != K_HFS_VERSION)
				{
					ex->Unload();
					return NULL;
				}

				ex->Seek(0, KSEEK_BEGIN);
				size = (kint)ex->GetSize();

				if (size > 104 * 1024 * 512)
				{
					// 512메가 까지만
					ex->Unload();
					return NULL;
				}

				st = kMemoryStream::New(size);

				for (;;)
				{
					if ((size = ex->Read(buf, 0, 64 * 1024)) == 0)
						break;

					st->Write(buf, 0, size);
				}

				st->Seek(0, KSEEK_BEGIN);

				ex->Unload();
			}
			else
			{
				// 메모리 HFS
				st = kMemoryStream::New(1024);
				k_return_value_if_fail(st, NULL);

				kCpInfo cp;
				k_getcp(&cp);

				Header hdr;
				hdr.hdr = K_HFS_HEADER;
				hdr.version = K_HFS_VERSION;
				hdr.revision = K_HFS_REVISION;
				hdr.signiture = K_MAKEWORD('S', 'B');
				hdr.method = 0;
				hdr.encode = KACP_UTF8;
				hdr.country = (kushort)cp.chcp;
				hdr.mask = 0;
				st->Write(&hdr, 0, sizeof(Header));

				WriteDirHeader(st, ".", 0, K_HFS_AT_ROOT, 0);
				st->Seek(0, KSEEK_BEGIN);
			}
		}
		else
		{
			// 파일
			st = kFileStream::New(filename, canwrite ? "r+@+" : "r@+");

#if SB_HFS_TRACE
			if (!st)
			{
				char sz[260];
				k_syserr(sz, 259, 0);
				k_mesg(sz);
				k_mesgch('\n');
			}
#endif

			k_return_value_if_fail(st, NULL);
		}

		if (st->Read(&hdr, 0, sizeof(Header)) != sizeof(Header) ||
			hdr.hdr != K_HFS_HEADER || hdr.version != K_HFS_VERSION)
		{
			st->Unload();
			return NULL;
		}

		return st;
	}

	// 찾기
	static bool FindDir(kHfsFile& retfile, const kchar* name, kuint hash, kStream* st)
	{
		for (;;)
		{
			if (st->Read(&retfile, 0, sizeof(kHfsFile)) != sizeof(kHfsFile))
				return false;

			if (retfile.src.type & KFAS_DIR)
			{
				UnmaskName(retfile.name, retfile.len, retfile.name);

				if (hash == retfile.hash && k_streqv(name, retfile.name))
					return true;
			}

			if (retfile.next == 0)
				return false;

			if (!st->Seek(retfile.next, KSEEK_BEGIN))
				return false;
		}
	}

	// 디렉터리 이름
	static void SetDirName(tpl::BlueStr<260>& dir, const kchar* name)
	{
		kchar* s;
		kchar* s2;

		if (name[0] == '.')
		{
			if (name[1] == '\0')
				return;

			if (name[1] == '.' && name[2] == '\0')
			{
				if (dir[0] == '/' && dir[1] == '\0')
					return;

				s2 = dir._data + dir._len - 1;

				for (s = s2 - 1; s != dir._data; --s)
				{
					if (*s == '/')
						break;
				}

				*(s + 1) = '\0';
				dir._len = dir._len - (int)(s2 - s);
				return;
			}
		}

		dir += name;
		dir += '/';
	}

	// 음
	static void DummyOptStack(int, const kchar*)
	{
	}
}

//
class kHfsFake : public kHfs
{
public:
	bool TestInit(const kchar* filename, const kchar* mode)
	{
		bool iswrt = false;
		bool iscrt = false;
		bool ismem = false;
		bool isext = false;

		if (mode)
		{
			const kchar* psz;

			for (psz = mode; *psz; psz++)
			{
				if (*psz == 'w' || *psz == 'c')
				{
					iswrt = true;
					iscrt = true;
				}
				else if (*psz == 'm' || *psz == 'i')
				{
					iswrt = true;
					ismem = true;
				}
				else if (*psz == 'e')
				{
					ismem = true;
					isext = true;
				}
				else if (*psz == '+')
					iswrt = true;
			}

			if (ismem)
			{
				// 메모리 로드는 만들지 않음
				iscrt = false;
			}
		}

		if (iscrt && !tcm::CreateHfs(filename))
			return false;

		kStream* st = tcm::OpenHfs(filename, iswrt, ismem, isext);
		k_return_value_if_fail(st, false);

		_stream = st;
		_canwrite = iswrt;
		_ismem = ismem;

		_path = "";	// 안하면 setdir에서 오류남
		SetDir("/");

		return true;
	}

	// 저장
	bool SaveDir(const tpl::BlueStr<260>& dir, tpl::BlueStr<260>& save)
	{
		if (dir.IsEmpty() || dir == GetDir())
		{
			save = (char*)NULL;
			return true;
		}
		else
		{
			save = GetDir();
			return SetDir(dir.Data());
		}
	}

	// 되돌림
	void RestoreDir(const tpl::BlueStr<260>& save)
	{
		if (save.IsHave())
			SetDir(save.Data());
	}
};

//
kHfs::kHfs()
: _hds(new FileArray)
{
}

//
kHfs::~kHfs()
{
	if (_stream)
		_stream->Unload();

	delete _hds;
}

//
kint kHfs::IsExist(const kchar* path)
{
	k_return_value_if_fail(path, KFAS_NONE);

	tpl::BlueStr<260> dir, name, save;

	tcm::SplitPath(path, dir, name);

	if (!static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return false;

	kint ret = KFAS_NONE;
	kuint hash = (kuint)name.Hash();

	kForEach(kHfsFile& file, *_hds)
	{
		if (hash == file.hash && name == file.name)
		{
			ret = file.src.type;
			break;
		}
	}

	static_cast<kHfsFake*>(this)->RestoreDir(save);

	return ret;
}

kbool kHfs::Remove(const kchar* path)
{
	k_return_value_if_fail(_canwrite, false);
	k_return_value_if_fail(path, false);

	tpl::BlueStr<260> dir, name, save;

	tcm::SplitPath(path, dir, name);

	if (name[0] == '.' || !static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return false;

	kuint hash = (kuint)name.Hash();

	auto it = _hds->Begin();

	for (; it != _hds->End(); ++it)
	{
		if (hash == it->hash && name == it->name)
			break;
	}

	if (it == _hds->End())
	{
		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return false;
	}

	kuint next = it->next;

	auto prev = it - 1;
	prev->next = next;

	if (!_stream->Seek(prev->src.srt + K_HFS_AT_NEXT, KSEEK_BEGIN) ||
		_stream->Write(&next, 0, sizeof(kuint)) != sizeof(kuint))
	{
		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return false;
	}

	_hds->Remove(it);
	static_cast<kHfsFake*>(this)->RestoreDir(save);

	return true;
}

kbool kHfs::SetDir(const kchar* dir)
{
	if (!dir)
		return false;

	if (_path == dir)
		return true;

	klong pos = _stream->Tell();

	if (_hds->IsHave())
	{
		auto it = _hds->begin();
		_stream->Seek(it->src.srt, KSEEK_BEGIN);
	}

	tpl::BlueStr<260> tmp(dir);

	if (tmp[0] == '/')
	{
		_stream->Seek(K_HFS_AT_ROOT, KSEEK_BEGIN);
		_path = '/';
	}

	kHfsFile file;
	kchar* savetok;

	for (kchar* tok = k_strtok(tmp.Data(), "\\/\x0\n\r\t", &savetok); tok; tok = k_strtok(NULL, "\\/\x0\n\r\t", &savetok))
	{
		kuint hash = (kuint)k_strhash(tok);

		if (!tcm::FindDir(file, tok, hash, _stream))
		{
			_stream->Seek(pos, KSEEK_BEGIN);
			return false;
		}

		_stream->Seek(file.meta, KSEEK_BEGIN);

		tcm::SetDirName(_path, tok);
	}

#if SB_HFS_TRACE
	k_mesgf("hfs:curdir [%s]\n", _path.Data());
	k_mesg("=spec=|=type=|=hash=|=filename=====================================\n");
	kint fc = 0;
#endif

	_hds->Clear();
	pos = _stream->Tell();

	for (kuint srt = (kuint)pos; srt; srt = file.next)
	{
		if (_stream->Read(&file, 0, sizeof(kHfsFile)) != sizeof(kHfsFile))
		{
			_stream->Seek(pos, KSEEK_BEGIN);
			return false;
		}

		tcm::UnmaskName(file.name, file.len, file.name);
		_stream->Seek(file.next, KSEEK_BEGIN);

		file.src.srt = srt;
		_hds->Add(file);

#if SB_HFS_TRACE
		k_mesgf(" %04X | %04X | %04X | %s\n", file.src.spec, file.src.type, file.hash, file.name);
		fc++;
#endif
	}

#if SB_HFS_TRACE
	k_mesgf("hfs:total %d files\n", fc);
#endif

	_stream->Seek(pos, KSEEK_BEGIN);

	return true;
}

kbool kHfs::MakeDir(const kchar* path)
{
	k_return_value_if_fail(_canwrite, false);
	k_return_value_if_fail(path, false);

	if (path[0] == '.')
		return false;

	if (path[0] == '/' && path[1] == '\0')
		return true;

	tpl::BlueStr<260> dir, name, save;

	tcm::SplitPath(path, dir, name);

	if (!static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return false;

	kuint hash = (kuint)name.Hash();

	kForEach(kHfsFile& file, *_hds)
	{
		if (hash == file.hash && name == file.name)
		{
			// 있음. 못 만듦... 인데 이거 패스 잡는거 틀렸음 고쳐야 함, 맞나? 모르겠네 귀찮으니 패스
			static_cast<kHfsFake*>(this)->RestoreDir(save);
			return false;
		}
	}

	if (!_stream->Seek(0, KSEEK_END))
	{
		// 이련 경우가 어쩌다 있음
		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return false;
	}

	// 이름
	kuint next = (kuint)_stream->Tell();
	tcm::WriteDirHeader(_stream, name, 0, next + sizeof(kHfsFile), 0);

	// 현재 디렉터리
	tcm::WriteDirHeader(_stream, ".", (kuint)_stream->Tell() + sizeof(kHfsFile), next + sizeof(kHfsFile), 0);

	// 부모
	tcm::WriteDirHeader(_stream, "..", 0, (*_hds)[0].src.srt, (*_hds)[0].stz);

	// 현재 갱신
	auto last = _hds->Last();
	_stream->Seek(last->src.srt + K_HFS_AT_NEXT, KSEEK_BEGIN);
	_stream->Write(&next, 0, sizeof(kuint));

	// 오케
	if (save.IsEmpty())
		save = ".";

	static_cast<kHfsFake*>(this)->RestoreDir(save);

	return true;
}

//
kStream* kHfs::OpenFile(const kchar* path, const kchar* mode)
{
	k_return_value_if_fail(path, NULL);

	if (mode)
	{
		for (; *mode; mode++)
		{
			if (*mode == 'a' || *mode == 'w' || *mode == '+')
			{
				// 쓰는거는 지원하지 않는다
				return NULL;
			}
		}
	}

	kint size;
	kbyte* buf = (kbyte*)ReadFile(path, &size);

	if (!buf)
		return NULL;

	kMemoryStream* mst = kMemoryStream::New();
	mst->ReplaceExtern(buf, size, size);
	mst->_loc = 0;                                  // 오픈이므로 이거 꼭 해야함. 아니면 못 읽음
	mst->_flags &= ~KFF_WRITE;
	mst->_mount = this;
	mst->_desc = path;
	this->Load();

	return mst;
}

// 이 함수는 주로 긴 오디오, 동영상 등 데이터가 무지무지 큰 것을 바로 접근하기 위해 사용함
kStream* kHfs::OpenFileIndirect(const char* path)
{
	k_return_value_if_fail(path, NULL);

	if (_ismem || _stream->GetType() != kFileStream::Type())
	{
		// 파일 스트림(kFileStream) 만 가능함!!!
		return kHfs::OpenFile(path, NULL);
	}

	//
	tpl::BlueStr<260> dir, name, save;
	tcm::SplitPath(path, dir, name);

	if (name.IsEmpty() || *name.First() == '.')
		return NULL;

	//
	if (!static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return NULL;

	kuint hash = (kuint)name.Hash();
	kHfsFile* data = NULL;

	kForEach(kHfsFile& file, *_hds)
	{
		if (hash == file.hash && name == file.name)
		{
			data = &file;
			break;
		}
	}

	static_cast<kHfsFake*>(this)->RestoreDir(save);

	if (!data)
		return NULL;

	//
	kStream* ret;

	if (data->src.type&KFAS_CMPR)
	{
		// 압축 지원안함, 그냥 파일로 전환
		kpointer buf = kHfs::SourceRead(data->src);

		if (!buf)
			ret = NULL;
		else
		{
			kMemoryStream* mst = kMemoryStream::New();
			mst->ReplaceExtern(buf, data->src.size, data->src.size);
			mst->_loc = 0;
			mst->_flags &= ~KFF_WRITE;
			mst->_mount = this;
			mst->_desc = path;
			this->Load();

			ret = mst;
		}
	}
	else
	{
		//
		kFile* file = k_file_new_dup(((kFileStream*)_stream)->GetInternalFile());

		if (!file)
		{
			// 파일 안만들어짐, 이건 오류를 밷을 수 밖에
			ret = NULL;
		}
		else
		{
			kIndirectStream* ist = kIndirectStream::NewExtern(file,
				data->src.srt + sizeof(kHfsFile), (ksize_t)data->src.size);

			ret = ist;
		}
	}

	return ret;
}

//
kpointer kHfs::ReadFile(const kchar* path, kint* size)
{
	k_return_value_if_fail(path, NULL);

	tpl::BlueStr<260> dir, name, save;

	tcm::SplitPath(path, dir, name);

	if (name.IsEmpty() || name[0] == '.')
		return NULL;

	if (!static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return NULL;

	kuint hash = (kuint)name.Hash();
	kHfsFile* dest = NULL;

	kForEach(kHfsFile& file, *_hds)
	{
		if (hash == file.hash && name == file.name)
		{
			dest = &file;
			break;
		}
	}
	if (!dest)
	{
		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return NULL;
	}

	kpointer ret = kHfs::SourceRead(dest->src);

	if (size)
		*size = (kint)dest->src.size;

	static_cast<kHfsFake*>(this)->RestoreDir(save);

	return ret;
}

//
kpointer kHfs::SourceRead(const kFileSource& src)
{
	_stream->Seek(src.srt + sizeof(kHfsFile), KSEEK_BEGIN);

	kpointer ret;

	if (src.type&KFAS_CMPR)
	{
		// 압축인가!
		kbyte* cbuf = k_new(src.cmpr, kbyte);

		if (!cbuf || _stream->Read(cbuf, 0, (kint)src.cmpr) != (kint)src.cmpr)
			ret = NULL;
		else
		{
			ret = k_zuncompress(cbuf, (ksize_t)src.cmpr, (ksize_t)src.size, NULL);
			k_free(cbuf);
		}
	}
	else
	{
		ret = (kpointer)k_new(src.size, kbyte);

		if (ret)
		{
			if (_stream->Read(ret, 0, (kint)src.size) != (kint)src.size)
			{
				k_free(ret);
				ret = NULL;
			}
		}
	}

	return ret;
}

//
kStream* kHfs::SourceOpen(const kFileSource& src, bool preload)
{
	kStream* ret;

	if (preload || _ismem || (src.type & (KFAS_CMPR | KFAS_ENCR))
		/*|| _stream->GetType() != kFileStream::Type()*/)
	{
		// 메모리 스트림
		// 1. 미리 로드
		// 2. 메모리 hfs
		// 3. 압축
		// 4. 압호화
		// 5. 스트림이 파일이 아님
		kbyte* buf = (kbyte*)SourceRead(src);
		k_return_value_if_fail(buf, NULL);

		kMemoryStream* mst = kMemoryStream::New();
		mst->ReplaceExtern(buf, (kint)src.size, (kint)src.size);
		mst->_loc = 0;
		mst->_flags &= ~KFF_WRITE;
		mst->_mount = this;
		this->Load();

		ret = mst;
	}
	else
	{
		// 그 외에는 간접 스트림
		kFile* file = k_file_new_dup(((kFileStream*)_stream)->GetInternalFile());

		if (!file)
		{
			// 파일 안만들어짐
			ret = NULL;
		}
		else
		{
			ret = kIndirectStream::NewExtern(file,
				src.srt + sizeof(kHfsFile), (ksize_t)src.size);
		}
	}

	return ret;
}

//
class kFileListHfs : public kFileList
{
	KCH_AUTOCLASS(kFileListHfs, kFileList, "FileListHfs");

public:
	kFileListHfs()
	{
	}

	void Init(kHfsFake* mnt, const kchar* mask)
	{
		tpl::BlueStr<260> path(mnt->GetDesc());
		path += mnt->GetDir();

		tpl::BlueStr<260> save;
		mnt->SaveDir(path, save);

		const kHfs::FileArray* files = mnt->GetHandles();

		for (auto it = files->cbegin(); it != files->cend(); ++it)
		{
			if (!k_strwcmatch(mask, it->name))
				continue;

			kFileInfo info;
			info.type = it->src.type;
			info.len = it->len;

			if (it->src.type & KFAS_DIR)
			{
				info.size = 0;
				info.cmpr = 0;
			}
			else
			{
				info.size = it->src.size;
				info.cmpr = it->src.cmpr;
			}

			info.stc = it->stz;
			info.stw = it->stz;
			k_strcpy(info.name, it->name);

			_infos->Add(info);
		}

		mnt->RestoreDir(save);

		_mount = mnt;
		mnt->Load();
	}
};

//
kFileList* kHfs::Files(const kchar* mask)
{
	if (!mask)
		mask = "*";

	kFileListHfs* ffs = kobj_create(kFileListHfs);
	ffs->Init(static_cast<kHfsFake*>(this), mask);

	return ffs;
}

//
kchar* kHfs::ExtractPath(const kchar* path)
{
	// 지원 안함
	return NULL;
}

//
kbool kHfs::StoreBuffer(const kchar* filename, kpointer buffer, kint size, kint store)
{
	k_return_value_if_fail(_canwrite, false);
	k_return_value_if_fail(filename, false);
	k_return_value_if_fail(buffer && size > 0, false);

	tpl::BlueStr<260> dir, name, save;

	tcm::SplitPath(filename, dir, name);

	if (name.IsEmpty() || name[0] == '.' || name.Length() > 250)
		return false;

	if (!static_cast<kHfsFake*>(this)->SaveDir(dir, save))
		return false;

	kuint hash = (kuint)name.Hash();

	for (auto it = _hds->begin(); it != _hds->end(); ++it)
	{
		if (hash == it->hash && name == it->name)
		{
			// 이미 있음
			static_cast<kHfsFake*>(this)->RestoreDir(save);
			return false;
		}
	}

	kpointer rbuf = NULL;
	ksize_t rsiz = 0;

	if (store == KSTORE_CMPR || store == KSTORE_AUTO)
	{
		rbuf = k_zcompress(buffer, size, &rsiz);

		if (!rbuf)
		{
			rsiz = 0;
			store = KSTORE_NORM;
		}
		else
		{
			double d = (double)size * 0.9;

			if ((double)rsiz < d)
				store = KSTORE_CMPR;
			else
			{
				store = KSTORE_NORM;
				rsiz = 0;
				k_free(rbuf);
			}
		}
	}
	else if (store == KSTORE_ENCR)
	{
		if (k_candebug())
			k_breakpoint();

		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return false;
	}
	else if (store == KSTORE_NORM)
	{
		// 음...
	}
	else
	{
		// 헉스
		static_cast<kHfsFake*>(this)->RestoreDir(save);
		return false;
	}

	//
	_stream->Seek(0, KSEEK_END);
	kuint next = (kuint)_stream->Tell();

	//
	kHfsFile file;
	file.src.spec = KFSTYLE_NONE;
	file.src.type = KFAS_FILE;
	file.ext = 0;
	file.meta = 0;
	file.next = 0;
	k_now((kDateTime*)&file.stz);

	if (rbuf && rsiz > 0)
	{
		// 압축 또는 암호화
		file.src.type |= store == KSTORE_CMPR ? KFAS_CMPR : KFAS_ENCR;
		file.src.size = size;
		file.src.cmpr = (kuint)rsiz;
		file.src.srt = 0;

		tcm::WriteFileHeader(_stream, file, name);
		_stream->Write(rbuf, 0, (kint)rsiz);

		k_free(rbuf);
	}
	else
	{
		// 그냥
		file.src.size = size;
		file.src.cmpr = 0;
		file.src.srt = 0;

		tcm::WriteFileHeader(_stream, file, name);
		_stream->Write(buffer, 0, size);
	}

	//
	auto it = _hds->Last();
	it->next = next;
	_stream->Seek(it->src.srt + K_HFS_AT_NEXT, KSEEK_BEGIN);
	_stream->Write(&next, 0, sizeof(kuint));

	//
	file.src.srt = next;
	file.hash = (kuint)name.Hash();
	k_strcpy(file.name, name.Data());
	_hds->Add(file);

	return true;
}

//
kbool kHfs::StoreStream(const kchar* filename, kStream* stream, kint store)
{
	k_return_value_if_fail(filename, false);
	k_return_value_if_fail(stream, false);

	kint size = (kint)stream->GetSize();

	if (size <= 0 || size > ((K_MAX_INT - 1) / 2))
		return false;

	tpl::ScopedMem<kbyte> mem(size);

	if (!mem.Data())
		return false;

	if (!stream->Seek(0, KSEEK_BEGIN) || stream->Read(mem.Data(), 0, size) != size)
		return false;

	return StoreBuffer(filename, mem.Data(), size, store);
}

//
kbool kHfs::StoreFile(const kchar* newname, kMount* mnt, const kchar* filename, kint store)
{
	kStream* stream = kMount::FileOpen(mnt, filename);
	k_return_value_if_fail(stream, false);

	char tmpname[260];

	if (!newname)
	{
		k_divpath(filename, NULL, 0, tmpname, 260 - 1);
		newname = tmpname;
	}

	bool ret = StoreStream(newname, stream, store);

	stream->Unload();

	return ret;
}

//
kbool kHfs::Optimize(const kchar* outputfilename, const tpl::Function<void(int, const kchar*)>& stackfn)
{
	k_return_value_if_fail(outputfilename, false);

	tcm::OptParam prm;
	prm.fn = stackfn;
	prm.stack = 1;
	prm.src = this;
	prm.dst = New(outputfilename, "w");

	k_return_value_if_fail(prm.dst, false);

	tpl::BlueStr<260> dir("/");
	tpl::BlueStr<260> save;
	static_cast<kHfsFake*>(this)->SaveDir(dir, save);

	prm.fn(0, "/");

	ProcessOptimize(prm);

	static_cast<kHfsFake*>(this)->RestoreDir(save);

	prm.dst->Unload();

	return true;
}

//
kbool kHfs::Optimize(const kchar* outputfilename)
{
	return Optimize(outputfilename, &tcm::DummyOptStack);
}

//
kHfs* kHfs::New(const kchar* filename, const kchar* mode/* =NULL */)
{
	/*
	mode
	: 'w' - create file hfs.
	: 'c' - same as 'w'.
	: 'm' - memory hfs. must use with '+'.
	: 'i' - same as 'm'
	: 'e' - extern file based memory hfs. can use with '+'.
	: '+' - write enable mode.
	*/
	kHfs* self = kobj_create(kHfs);

	if (!static_cast<kHfsFake*>(self)->TestInit(filename, mode))
	{
		self->Unload();
		self = NULL;
	}

	return self;
}


//////////////////////////////////////////////////////////////////////////
// fuse
KIMPL_SUBCLASS_ABSTRACT(kFuse, "AbstractFuse");

kFuse::kFuse()
{
}

kFuse::~kFuse()
{
}

// 진짜 fuse
class kRealFuse : public kFuse
{
	KCH_AUTOCLASS(kRealFuse, kFuse, "FuseMount");

public:
	struct IndexInfo : kFileSource
	{
		kMount*		mnt;

		IndexInfo(kFileSource src, kMount* mnt)
		{
			*((kFileSource*)this) = src;
			this->mnt = mnt;
		}
	};

#if _SB_DEBUG_
	// 내용을 확인하기 위해 해시 테이블로...
	typedef tpl::HashTable<PathString, IndexInfo> PathHash;
#else
	typedef tpl::Mukum<PathString, IndexInfo> PathHash;
#endif
	typedef tpl::HashTable<PathString, kMount*> MountHash;

private:
	MountHash			_mnts;
	PathHash			_files;

private:
	kRealFuse()
	{
	}

	~kRealFuse()
	{
		kForEach(MountHash::value_type& p, _mnts)
			p.second->Unload();
	}

public:
	K_OVR kint IsExist(const kchar* path)
	{
		kint ret = kDiskFs::IsExist(path);

		if (ret)
			ret = ret&KFAS_FILE ? KFAS_FILE : 0;
		else
		{
			auto it = _files.Get(path);
			ret = it == _files.Null() ? 0 : KFAS_FILE;
		}

		return ret;
	}

	K_OVR kbool Remove(const kchar* path)
	{
		kbool ret = kDiskFs::Remove(path);

		if (!ret)
		{
			auto it = _files.Get(path);

			if (it == _files.Null())
				ret = false;
			else
				ret = it->second.mnt->Remove(path);
		}

		return ret;
	}

	K_OVR kStream* OpenFile(const kchar* path, const kchar* mode)
	{
		kStream* ret = kDiskFs::OpenFile(path, mode);

		if (!ret)
		{
			auto it = _files.Get(path);

			if (it == _files.Null())
				ret = NULL;
			else
			{
				ret = it->second.mnt->SourceOpen(it->second, true);
				if (ret)
					ret->SetDesc(path);
			}
		}

		return ret;
	}

	K_OVR kStream* OpenFileIndirect(const char* path)
	{
		kStream* ret = kDiskFs::OpenFile(path, NULL);

		if (!ret)
		{
			auto it = _files.Get(path);

			if (it == _files.Null())
				ret = NULL;
			else
			{
				ret = it->second.mnt->SourceOpen(it->second, false);
				if (ret)
					ret->SetDesc(path);
			}
		}

		return ret;
	}

	K_OVR kpointer ReadFile(const kchar* path, kint* size)
	{
		kpointer ret = kDiskFs::ReadFile(path, size);

		if (!ret)
		{
			auto it = _files.Get(path);

			if (it == _files.Null())
			{
				ret = NULL;
				if (size)
					*size = 0;
			}
			else
			{
				ret = it->second.mnt->SourceRead(it->second);
				if (size)
					*size = it->second.size;
			}
		}

		return ret;
	}

	K_OVR bool AddMount(const char* path)
	{
		k_return_value_if_fail(path, false);

		// 확장자
		char* psz = k_strrchr(path, '.');
		k_return_value_if_fail(psz, false);

		//
		PathString bs;

		if (path[0] == '/' || path[0] == '\\'
#if _SB_WINDOWS_
			|| path[1] == ':'
#endif
			)
		{
			// 절대 경로
			bs = path;
		}
		else
		{
			// 상대 경로
			bs = _desc;
			bs += K_PATH_SEP;	// _path를 더해도 되지만 언제나 값이 변하지 않기 때문에 바로 적용
			bs += path;
		}

		auto it = _mnts.Get(bs);

		if (it != _mnts.Null())
		{
			// 이미 있음
			return false;
		}

		// 확장자별
		kMount* mnt;
		tpl::BlueStr<64> ext(psz + 1);

		if (ext.CompareTo("hfs", true) == 0)
		{
			// HFS
			mnt = kHfs::New(bs.Data());

			if (mnt)
				_ParseHfs((kHfs*)mnt, "/");
		}
		else
		{
			// 헐...
			mnt = NULL;
		}

		//
		k_return_value_if_fail(mnt, false);

		_mnts.Set(bs, mnt);

		return true;
	}

	K_OVR kint GetMountCount()
	{
		return _mnts.Count();
	}

private:
	void _ParseHfs(kHfs* hfs, const char* dir)
	{
		const kHfs::FileArray* srcs = hfs->GetHandles();
		kHfs::FileArray fls(srcs->ConstBegin(), srcs->ConstEnd());
		//kHfs::FileArray fls(*srcs);

		PathString bs(dir);
		kint bpos = bs.Length();

		kForEach(kHfsFile& f, fls)
		{
			if (f.name[0] == '.')
				continue;

			bs += f.name;

			if (f.src.type & KFAS_DIR)
			{
				bs += '/';

				hfs->SetDir(f.name);
				_ParseHfs(hfs, bs.Data());
				hfs->SetDir("..");
			}
			else if (f.src.type & KFAS_FILE)
			{
				_files.Set(bs, IndexInfo(f.src, hfs));
			}

			bs.Truncate(bpos);
		}
	}
};

kint kFuse::AutoMount(const char* mask)
{
	kFileList* fl;
	kint cnt = 0;

	if (!mask)
		mask = "*.hfs";

	fl = kDiskFs::Files(mask);

	if (fl)
	{
		kFileInfo f;
		bool b = fl->First(&f);

		while (b)
		{
			if (AddMount(f.name))
				cnt++;

			b = fl->Next(&f);
		}

		fl->Unload();
	}

	return cnt;
}

//
kFuse* kFuse::New(const char* basedir)
{
	kFuse* fuse = kobj_create(kRealFuse);

	if (!static_cast<kDiskFsFake*>(static_cast<kDiskFs*>(fuse))->InitDir(basedir))
	{
		fuse->Unload();
		fuse = NULL;
	}

	return fuse;
}
